% !TeX root = ../main.tex

\chapter{建模与验证}

% TODO 解释C模型在ASIC设计中的作用                                          
据统计，全世界有67\%以上的ASIC项目都是拖期交付的，其中有些项目会拖期半年以上，从这个数据可以想象得到，通向一款ASIC芯片的道路会有多么曲折。
ASIC芯片的特点与简单指令集的CPU完全相反，它被设计出来的目的就是为了实现复杂功能。
因此，ASIC芯片的设计过程异常繁琐。
一般的，芯片公司会在项目初始时，完成市场需求设计和产品需求设计。
在需求被明确之后，系统工程师就会开始进行架构设计。
这个过程包含了提出顶层架构设计和定义需求中所有用例涉及到的子模块。
并且，定义各个子模块之间的连接关系、交互方式和时序要求。
在完成芯片的架构设计之后，需要对芯片进行建模。芯片建模是用一种高级语言对芯片的行为进行描述。
一般会采用C/C++或者SystemC来实现建模。
如果涉及到新的算法模块，会要求算法工程师先做出算法的Matlab或者C模型，作为模块开发者和芯片模型的参考依据。

\section{顶层架构}
在章节2.1中，本文已经简述了总体的架构设计。本章节将详细说明顶层架构的C模型。


\section{数据的输入}
需要设计相应的模块处理输入的图像数据，使得神经网络计算模块可以进行计算。
%TODO 描述DVP并口传输input的数据
%DRAFT 设计有下列端口
%       PCLK        像素点同步时钟信号
%       XCLK        外部输入时钟信号
%       VSYNC       帧同步信号
%       HSYNC       行同步信号
%       Data[0:11]  数据信号12bit


%TODO 设计Input Data Adapter
%       buffer      5行buffer


\section{数据的运算}  


% ResNet18 代码实现，转移到第三章
\begin{codeblock}[language=python]
    class BasicBlock(nn.Module):
        expansion: int = 1
    
        def __init__(
            self,
            inplanes: int,
            planes: int,
            stride: int = 1,
            downsample: Optional[nn.Module] = None,
            groups: int = 1,
            base_width: int = 64,
            dilation: int = 1,
            norm_layer: Optional[Callable[..., nn.Module]] = None,
        ):
            super().__init__()
            if norm_layer is None:
                norm_layer = nn.BatchNorm2d
            if groups != 1 or base_width != 64:
                raise ValueError("BasicBlock only supports groups=1 and base_width=64")
            if dilation > 1:
                raise NotImplementedError("Dilation > 1 not supported in BasicBlock")
            # Both self.conv1 and self.downsample layers downsample the input when stride != 1
            self.conv1 = conv3x3(inplanes, planes, stride)
            self.bn1 = norm_layer(planes)
            self.relu = nn.ReLU(inplace=True)
            self.conv2 = conv3x3(planes, planes)
            self.bn2 = norm_layer(planes)
            self.downsample = downsample
            self.stride = stride
    
        def forward(self, x: Tensor):
            identity = x
    
            out = self.conv1(x)
            out = self.bn1(out)
            out = self.relu(out)
    
            out = self.conv2(out)
            out = self.bn2(out)
    
            if self.downsample is not None:
                identity = self.downsample(x)
    
            out += identity
            out = self.relu(out)
    
            return out
    
    \end{codeblock}
    
    
    
    下面使用pytorch来实现resnet18，以此来观察各个层之间的数据流。
    \begin{codeblock}[language=python]
    class ResNet18(nn.Module):
        def __init__(self, block, layers, num_classes=1000):
            self.inplanes = 64
            super(ResNet, self).__init__()
            self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
                                   bias=False)
            self.bn1 = nn.BatchNorm2d(64)
            self.relu = nn.ReLU(inplace=True)
            self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
            self.layer1 = self._make_layer(block, 64, layers[0])
            self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
            self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
            self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
            self.avgpool = nn.AvgPool2d(7, stride=1)
            self.fc = nn.Linear(512 * block.expansion, num_classes)
    
            for m in self.modules():
                if isinstance(m, nn.Conv2d):
                    n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
                    m.weight.data.normal_(0, math.sqrt(2. / n))
                elif isinstance(m, nn.BatchNorm2d):
                    m.weight.data.fill_(1)
                    m.bias.data.zero_()
    
        def _make_layer(self, block, planes, blocks, stride=1):
            downsample = None
            if stride != 1 or self.inplanes != planes * block.expansion:
                downsample = nn.Sequential(
                    nn.Conv2d(self.inplanes, planes * block.expansion,
                              kernel_size=1, stride=stride, bias=False),
                    nn.BatchNorm2d(planes * block.expansion),
                )
    
            layers = []
            layers.append(block(self.inplanes, planes, stride, downsample))
            self.inplanes = planes * block.expansion
            for i in range(1, blocks):
                layers.append(block(self.inplanes, planes))
    
            return nn.Sequential(*layers)
    
        def forward(self, x):
            x = self.conv1(x)
            x = self.bn1(x)
            x = self.relu(x)
            x = self.maxpool(x)
    
            x = self.layer1(x)
            x = self.layer2(x)
            x = self.layer3(x)
            x = self.layer4(x)
    
            x = self.avgpool(x)
            x = x.view(x.size(0), -1)
            x = self.fc(x)
    
            return x
    
    \end{codeblock}
    


\subsection{脉动阵列的仿真}
% TODO 首先实现脉动和PE
定义处理单元和实现脉动的行为。
\begin{codeblock}[language=C]
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <iomanip>
#define N 256
using namespace std;

typedef struct PE{
    int weight;
    int neuron;
    int psum;
};

class Systolic{
public:
    PE S[N][N];
public:
    void Init(){
        for(int i=0;i<N;i++)
            for(int j=0;j<N;j++){
                S[i][j].psum=0;
                S[i][j].weight=0;
                S[i][j].neuron=0;
            }
    }
    void calc(){
        for(int i=0;i<N;i++)
            for(int j=0;j<N;j++)
                S[i][j].psum+=S[i][j].weight*S[i][j].neuron;
    }
    void shift(int a[N],int b[N]){
        //水平方向传播矩阵A,a[N]是本次要被读入的列(left->right)
        for(int i=0;i<N;i++)
            for(int j=N-1;j>0;j--){
                S[i][j].neuron=S[i][j-1].neuron;
        }
        for(int i=0;i<N;i++)
            S[i][0].neuron=a[i];
        //竖直方向上传播矩阵B,b[N]是本次要被读入的行(up->bottom)
        for(int j=0;j<N;j++)
            for(int i=N-1;i>0;i--){
                S[i][j].weight=S[i-1][j].weight;
        }
        for(int j=0;j<N;j++)
            S[0][j].weight=b[j];
    }
    void Display(){
        cout<<"weight:"<<endl;
        for(int i=0;i<N;i++){
            for(int j=0;j<N;j++)
                cout<<fixed<<setw(4)<<S[i][j].weight<<",";
            cout<<endl;
        }
        cout<<"neuron:"<<endl;
        for(int i=0;i<N;i++){
            for(int j=0;j<N;j++)
                cout<<fixed<<setw(4)<<S[i][j].neuron<<",";
            cout<<endl;
        }
    }
};

\end{codeblock}



